package com.xhj.mybatis.builder.xml;

import com.xhj.mybatis.builder.BaseBuilder;
import com.xhj.mybatis.datasource.DataSourceFactory;
import com.xhj.mybatis.io.Resources;
import com.xhj.mybatis.mapping.DatabaseIdProvider;
import com.xhj.mybatis.mapping.Environment;
import com.xhj.mybatis.plugin.Interceptor;
import com.xhj.mybatis.reflection.MetaClass;
import com.xhj.mybatis.reflection.ReflectorFactory;
import com.xhj.mybatis.session.Configuration;
import com.xhj.mybatis.session.LocalCacheScope;
import com.xhj.mybatis.transaction.TransactionFactory;
import com.xhj.mybatis.type.JdbcType;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.xml.sax.InputSource;

import javax.sql.DataSource;
import java.io.InputStream;
import java.io.Reader;
import java.util.*;

/**
 * XMLConfigBuilder
 *
 * @author XJks
 * @description XML配置构建器
 */
public class XMLConfigBuilder extends BaseBuilder {

    /**
     * XML 根节点
     */
    private Element root;

    /**
     * 本地反射工厂实例
     */
    private final ReflectorFactory localReflectorFactory = ReflectorFactory.GLOBAL_INSTANCE;

    public XMLConfigBuilder(Reader reader) {
        // 调用父类构造方法初始化Configuration
        super(new Configuration());
        // 使用 dom4j 处理 xml
        SAXReader saxReader = new SAXReader();
        try {
            Document document = saxReader.read(new InputSource(reader));
            root = document.getRootElement();
        } catch (DocumentException e) {
            e.printStackTrace();
        }
    }

    /**
     * 解析配置：类型别名、插件、对象工厂、对象包装工厂、设置、环境、类型转换、映射器
     *
     * @return Configuration
     */
    public Configuration parse() {
        try {
            // 解析 <settings> 节点，将其内容转为 Properties 对象
            Properties settings = settingsAsProperties(root.element("settings"));
            // 解析 <plugins> 节点，注册插件（拦截器）
            pluginElement(root.element("plugins"));
            // 应用 <settings> 节点中的各项配置到 Configuration 对象
            settingsElement(settings);
            // 解析 <environments> 节点，配置数据源和事务管理器
            environmentsElement(root.element("environments"));
            // 解析 <databaseIdProvider> 节点，配置数据库厂商标识
            databaseIdProviderElement(root.element("databaseIdProvider"));
            // 解析 <mappers> 节点，注册 Mapper 映射器
            mapperElement(root.element("mappers"));
        } catch (Exception e) {
            throw new RuntimeException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
        }
        return configuration;
    }

    /**
     * 将 <settings> 节点内容转换为 Properties，并校验每个配置项是否合法
     *
     * @param context <settings> 节点
     * @return Properties 对象，包含 <settings> 节点的所有配置项
     */
    private Properties settingsAsProperties(Element context) {
        // 如果 <settings> 节点为空，直接返回一个空的 Properties 对象
        if (context == null) {
            return new Properties();
        }
        // 获取 <settings> 节点下所有子节点的属性，封装为 Properties 对象
        Properties props = getChildrenAsProperties(context);
        // 创建 MetaClass，用于反射检查 Configuration 类是否有对应的 setter 方法
        MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
        // 遍历所有配置项的 key
        for (Object key : props.keySet()) {
            // 检查 Configuration 类中是否有该 key 对应的 setter 方法
            if (!metaConfig.hasSetter(String.valueOf(key))) {
                // 如果没有，抛出异常，提示配置项拼写错误或不存在
                throw new RuntimeException("The setting " + key + " is not known.  Make sure you spelled it correctly (case sensitive).");
            }
        }
        // 校验通过后，返回 Properties 对象
        return props;
    }

    /**
     * 解析 <plugins> 节点，注册插件（拦截器）
     *
     * @param parent <plugins> 节点
     * @throws Exception 异常
     */
    private void pluginElement(Element parent) throws Exception {
        // 如果 <plugins> 节点不为空，才进行处理
        if (parent != null) {
            // 遍历 <plugins> 节点下的所有子节点（每个子节点代表一个插件）
            for (Element child : parent.elements()) {
                // 获取 interceptor 属性（插件实现类的全限定名）
                String interceptor = child.attributeValue("interceptor");
                // 获取插件节点下的所有属性，封装为 Properties 对象（插件的自定义参数）
                Properties properties = getChildrenAsProperties(child);
                // 通过反射创建插件实例（Interceptor 接口的实现类）
                Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).getDeclaredConstructor().newInstance();
                // 设置插件的属性（如果有配置参数）
                interceptorInstance.setProperties(properties);
                // 将插件添加到 MyBatis 的配置中，后续会在执行 SQL 时生效
                configuration.addInterceptor(interceptorInstance);
            }
        }
    }

    /**
     * 处理 <settings> 节点，将配置项应用到 Configuration 对象
     *
     * @param props <settings> 节点对应的 Properties 对象
     */
    private void settingsElement(Properties props) {
        // 是否开启二级缓存，默认 true
        configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));
        // 是否使用列标签而不是列名，默认 true
        configuration.setUseColumnLabel(booleanValueOf(props.getProperty("useColumnLabel"), true));
        // 是否使用 JDBC 的 getGeneratedKeys 获取自增主键，默认 false
        configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty("useGeneratedKeys"), false));
        // 是否开启下划线转驼峰映射，默认 false
        configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false));
        // 设置本地缓存作用域（SESSION 或 STATEMENT），默认 SESSION
        configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION")));
        // 设置为 null 时的 JDBC 类型，默认 OTHER
        configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER")));
        // 是否在 set 时对 null 调用 setter，默认 false
        configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false));
        // 是否使用实际参数名（JDK8+），默认 true
        configuration.setUseActualParamName(booleanValueOf(props.getProperty("useActualParamName"), true));
        // 是否收缩 SQL 语句中的多余空白字符，默认 false
        configuration.setShrinkWhitespacesInSql(booleanValueOf(props.getProperty("shrinkWhitespacesInSql"), false));
    }

    /**
     * 解析 <environments> 节点，配置数据源和事务管理器
     */
    private void environmentsElement(Element environments) throws Exception {
        // 获取 default 属性值，表示默认使用哪个环境
        String environment = environments.attributeValue("default");
        // 获取所有的 <environment> 子节点
        List<Element> environmentElementList = environments.elements("environment");
        // 遍历 <environments> 节点下的所有子节点
        for (Element environmentElement : environmentElementList) {
            // 获取 id 属性值，和 default 属性值进行比较，找到需要使用的环境
            String id = environmentElement.attributeValue("id");
            if (environment.equals(id)) {
                // 根据 <environment> 标签的 <transactionManager> 子标签中的 type 属性值，从 typeAliasRegistry 中获取 TransactionFactory 对应的 Class 对象，然后通过 newInstance 创建对象
                TransactionFactory txFactory = (TransactionFactory) typeAliasRegistry.resolveAlias(environmentElement.element("transactionManager").attributeValue("type")).newInstance();
                // 获取 <environment> 标签中的 <dataSource> 子标签
                Element dataSourceElement = environmentElement.element("dataSource");
                // 根据 <dataSource> 标签中的 type 属性值，从 typeAliasRegistry 中获取 DataSourceFactory 对应的 Class 对象，然后通过 newInstance 创建对象
                DataSourceFactory dataSourceFactory = (DataSourceFactory) typeAliasRegistry.resolveAlias(dataSourceElement.attributeValue("type")).newInstance();
                // 解析 <dataSource> 标签中的所有 <property> 子标签，封装成 Properties 对象
                List<Element> propertyElementList = dataSourceElement.elements("property");
                Properties props = new Properties();
                for (Element propertyElement : propertyElementList) {
                    props.setProperty(propertyElement.attributeValue("name"), propertyElement.attributeValue("value"));
                }
                // 使用配置的属性值，创建数据源
                dataSourceFactory.setProperties(props);
                DataSource dataSource = dataSourceFactory.getDataSource();
                // 创建 Environment.Builder，并设置 id、事务管理器和数据源
                Environment.Builder environmentBuilder = new Environment.Builder(id)
                        .transactionFactory(txFactory)
                        .dataSource(dataSource);
                // 构建 Environment 对象，并设置到 configuration 中
                configuration.setEnvironment(environmentBuilder.build());
            }
        }
    }

    /**
     * 解析 {@code <databaseIdProvider>} 节点，配置数据库厂商标识
     *
     * @param context {@code <databaseIdProvider>} 节点
     * @throws Exception 异常
     */
    private void databaseIdProviderElement(Element context) throws Exception {
        // 声明一个 DatabaseIdProvider 变量，初始为 null
        DatabaseIdProvider databaseIdProvider = null;
        // 如果 <databaseIdProvider> 节点不为空，才进行处理
        if (context != null) {
            // 获取 type 属性（数据库厂商标识提供者的类型，可能是别名或全限定类名）
            String type = context.attributeValue("type");
            // 兼容旧配置，如果 type 是 VENDOR，则改为 DB_VENDOR
            if ("VENDOR".equals(type)) {
                type = "DB_VENDOR";
            }
            // 获取 <databaseIdProvider> 节点下所有子节点的属性，封装为 Properties 对象
            Properties properties = getChildrenAsProperties(context);
            // 通过反射创建 DatabaseIdProvider 实例（先获取类，再调用无参构造方法实例化）
            databaseIdProvider = (DatabaseIdProvider) resolveClass(type).getDeclaredConstructor().newInstance();
            // 设置 DatabaseIdProvider 的属性（如果有配置参数）
            databaseIdProvider.setProperties(properties);
        }
        // 获取当前 MyBatis 配置中的 Environment（环境配置，包括数据源等）
        Environment environment = configuration.getEnvironment();
        // 如果 environment 和 databaseIdProvider 都不为 null，才进行数据库厂商标识的设置
        if (environment != null && databaseIdProvider != null) {
            // 通过 databaseIdProvider 获取当前数据源对应的数据库厂商标识
            String databaseId = databaseIdProvider.getDatabaseId(environment.getDataSource());
            // 将获取到的数据库厂商标识设置到 MyBatis 的配置对象中
            configuration.setDatabaseId(databaseId);
        }
    }

    /**
     * 解析 <mappers> 节点，注册 Mapper 映射器
     */
    private void mapperElement(Element mappers) throws Exception {
        // 获取所有的 <mapper> 子节点
        List<Element> mapperElementList = mappers.elements("mapper");
        // 遍历 <mappers> 节点下的所有子节点
        for (Element mapperElement : mapperElementList) {
            // TODO:源码中还存在 package 和 url 的处理逻辑，这里简化只处理 resource 和 class 属性。
            // 获取 resource 属性（指定 Mapper XML 文件的路径）
            String resource = mapperElement.attributeValue("resource");
            // 获取 class 属性（指定 Mapper 接口的全限定名）
            String mapperClass = mapperElement.attributeValue("class");
            if (resource != null && mapperClass == null) {
                // 以资源文件方式加载 Mapper XML
                try (InputStream inputStream = Resources.getResourceAsStream(resource)) {
                    // 创建 XMLMapperBuilder 解析器，解析 Mapper XML 文件
                    XMLMapperBuilder mapperParser = new XMLMapperBuilder(configuration, resource, inputStream);
                    // 执行解析
                    mapperParser.parse();
                }
            } else if (resource == null && mapperClass != null) {
                // 通过类名加载 Mapper 接口的 Class 对象
                Class<?> mapperInterface = Resources.classForName(mapperClass);
                // 注册该 Mapper 接口到 configuration
                configuration.addMapper(mapperInterface);
            } else {
                // 抛出异常
                throw new RuntimeException("A mapper element may only specify a resource or class, but not more than one.");
            }
        }
    }
}
